﻿using Space_X.Properties;
using Space_X.ViewModels;
using Space_X.Views;
using Space_X.Views.Dialogs;
using spacex.imaging;
using spacex.util.Animations;
using spacex.util.Logger;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Interactivity;

namespace Space_X
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class CameraView : Window
    {
        #region Globals
        private readonly double ZOOM_IN_FACTOR = 1.1;
        private readonly double ZOOM_OUT_FACTOR = 0.9;
        #endregion //Globals

        #region Class members
        protected CameraViewModel cameraViewModel;
        protected ImageProcessor imgProcessor;
        protected bool scalingAA = true;
        protected bool showPixelOverlay = false;
        protected bool autoExposure = false;
        protected PixelViewer pixelOverlay;
        protected PanCamLogger panCamLogger;
        #endregion

        #region Constrctor
        /// <summary>
        /// The class represents a camera view
        /// </summary>
        public CameraView()
        {
            // Generate the view from the XAML file
            InitializeComponent();

            //Get the logger
            panCamLogger = PanCamLogger.GetInstance();
            LogMessage("Interface initilaized");

            //get the base directory from user preferences
            string baseDirectory = (string)Settings.Default.Path;

            //load window height and width from preferences
            this.Width = Settings.Default.WindowSize.X;
            this.Height = Settings.Default.WindowSize.Y;

            // change path to: C:\Users\pancam-user\PanCam\img
            Settings.Default.Path = @"C:\Users\pancam-user\PanCam\";
            Settings.Default.Save();

            // Initialize the pixel overlay
            pixelOverlay = new PixelViewer();

            // Initalize the view model
            cameraViewModel = new CameraViewModel();

            // Get an instance of the image processor
            imgProcessor = ImageProcessor.GetInstance();

            // Give initial focus to PanCam Tab
            FocusManager.SetFocusedElement(this, PanCamTab);

            // Set and initial zoom value
            ImageBox.Width = ZoomSlider.Value + 100;
            ImageBox.Height = ZoomSlider.Value + 100;

            ImageBoxStereo.Width = ZoomSlider.Value + 100;
            ImageBoxStereo.Height = ZoomSlider.Value + 100;

            // Globally register click handlers on all buttons (must be preview in order to catch)
            EventManager.RegisterClassHandler(typeof(Button), Button.PreviewMouseDownEvent, new RoutedEventHandler(IconMouseDown));
            EventManager.RegisterClassHandler(typeof(Button), Button.PreviewMouseUpEvent, new RoutedEventHandler(IconMouseUp));

            // Set the data context of the CameraViewModel
            this.DataContext = cameraViewModel;
        }
        #endregion

        #region Commands
        /// <summary>
        /// The closing dialog
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Close_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
            e.Handled = true;
        }

        /// <summary>
        /// Check if the application can be closed
        /// </summary>
        /// <param name="sender">The closing menu</param>
        /// <param name="e">The closing parameters</param>
        private void Close_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            Application.Current.Shutdown();
        }
        #endregion

        #region Action listeners
        /// <summary>
        /// Zoom slider
        /// </summary>
        /// <param name="sender">The slider</param>
        /// <param name="e">The slider values</param>
        private void Slider_ValueChanged_1(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (this.IsLoaded)
            {
                ImageBox.Width = e.NewValue + 200;
                ImageBox.Height = e.NewValue + 200;

                ImageBoxStereo.Width = e.NewValue + 200;
                ImageBoxStereo.Height = e.NewValue + 200;

                Scroller.ScrollToHorizontalOffset(Scroller.ScrollableWidth / 2);
                Scroller.ScrollToVerticalOffset(Scroller.ScrollableHeight / 2);
            }
        }

        /// <summary>
        /// Pancam tab pressed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TabItem_PanCam_Pressed(object sender, MouseButtonEventArgs e)
        {
            cameraViewModel.inStereo = false;
        }

        /// <summary>
        /// Histogram tab pressed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TabItem_Histogram_Pressed(object sender, MouseButtonEventArgs e)
        {
            if(PanCam.Source != null){
                Hist.Source = imgProcessor.GenerateHistogram(PanCam.Source as BitmapImage).Source;
            }
        }

        /// <summary>
        /// Enables or disabled all control elements
        /// </summary>
        /// <param name="enabled"></param>
        private void ControlElementsEnabled(bool enabled)
        {
            /*
            foreach (UIElement element in CommandContainer.Children)
            {
                element.IsEnabled = enabled;
            }
            ZoomSlider.IsEnabled = enabled;*/
        }
        #endregion
        /// <summary>
        /// Allows the window to be dragged on the menu bar
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Border_MouseLeftButtonDown_1(object sender, MouseButtonEventArgs e)
        {
            this.DragMove();
        }

        /// <summary>
        /// Ajusts the icon on a mouse down event on an icon (style changed are handled by triggers)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void IconMouseDown(object sender, RoutedEventArgs e)
        {
            string uid = (sender as Button).Uid;

            if (uid.Length >= 5 && uid.Substring(0, 4) == "icon")
            {
                string name = (sender as Button).Uid.Substring(5) + "Down.png";
                Image content = ((sender as Button).Content) as Image;

                UpdateImage(content, name);
            }
        }

        /// <summary>
        /// Handles a mouse down event on an icon Button
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void IconMouseUp(object sender, RoutedEventArgs e)
        {
            string uid = (sender as Button).Uid;

            if (uid.Length >= 5 && uid.Substring(0, 4) == "icon")
            {
                string name = (sender as Button).Uid.Substring(5) + ".png";
                Image content = ((sender as Button).Content) as Image;
                UpdateImage(content, name);
            }
        }

        /// <summary>
        /// Handles and mouse up on an icon button and channges the icon
        /// </summary>
        /// <param name="image"></param>
        /// <param name="filename"></param>
        public void UpdateImage(Image image, string filename)
        {
            Uri source = new Uri(@"pack://application:,,,/spacex.util;component/Icons/" + filename);
            image.Source = new BitmapImage(source);
        }

        /// <summary>
        /// Minimize the main view
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Minimize(object sender, ExecutedRoutedEventArgs e)
        {
            SystemCommands.MinimizeWindow(this);
        }

        /// <summary>
        /// Toggles between windowed and fullceen mode
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void WindowToggle(object sender, ExecutedRoutedEventArgs e)
        {
            SystemCommands.MinimizeWindow(this);
        }

        /// <summary>
        /// Toggle the sidebar when one of the toggle icons has been clicked
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void GridWidthToogle(object sender, RoutedEventArgs e)
        {
            if (ControlColumn.ActualWidth < 250)
            {
                ControlColumn.Width = new GridLength(250);
                Sidebar.Visibility = System.Windows.Visibility.Visible;
            }
            else
            {
                ControlColumn.Width = new GridLength(50);
                Sidebar.Visibility = System.Windows.Visibility.Hidden;
            }
        }

        #region Zooming
        /// <summary>
        /// Handles zooming by scrolling (only works when left ctrl key is being held down)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ScrollOnImage(object sender, MouseWheelEventArgs e)
        {
            // shroll up (zoom in)
            if (Keyboard.IsKeyDown(Key.LeftCtrl))
            {
                e.Handled = true;

                if (e.Delta > 0)
                {
                    this.ZoomSlider.Value *= ZOOM_IN_FACTOR;
                }
                else
                {
                    this.ZoomSlider.Value *= ZOOM_OUT_FACTOR;
                }

                Scroller.ScrollToHorizontalOffset(Scroller.ScrollableWidth / 2);
                Scroller.ScrollToVerticalOffset(Scroller.ScrollableHeight / 2);
            }
            else
            {
                e.Handled = false;
            }
        }

        /// <summary>
        /// Implements the zoom in when the user clicks on the zoom in icon
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ZoomIn_Click(object sender, RoutedEventArgs e)
        {
            this.ZoomInStep();
        }

        /// <summary>
        /// Implements the zoom out when the user clicks on the zoom out icon
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ZoomOut_Click(object sender, RoutedEventArgs e)
        {
            this.ZoomOutStep();
        }

        /// <summary>
        /// Zooms into the image one step
        /// </summary>
        private void ZoomInStep()
        {
            ZoomSlider.Value = ZoomSlider.Value *= ZOOM_IN_FACTOR;

            Scroller.ScrollToHorizontalOffset(Scroller.ScrollableWidth / 2);
            Scroller.ScrollToVerticalOffset(Scroller.ScrollableHeight / 2);
        }

        /// <summary>
        /// Zooms out of the image one step
        /// </summary>
        private void ZoomOutStep()
        {
            ZoomSlider.Value = ZoomSlider.Value *= ZOOM_OUT_FACTOR;

            Scroller.ScrollToHorizontalOffset(Scroller.ScrollableWidth / 2);
            Scroller.ScrollToVerticalOffset(Scroller.ScrollableHeight / 2);
        }
        #endregion

        /// <summary>
        /// Logs a message and adds a timestamp in the sidebar
        /// </summary>
        /// <param name="message">The message to be logged</param>
        public void LogMessage(string message)
        {
            DateTime time = DateTime.Now;

            string format = "HH:mm:ss";
            string outString = time.ToString(format);

            string messageToLog = message + " " + outString;

            Logger.Items.Add(messageToLog);
            panCamLogger.LogMessage(messageToLog);

            int items = Logger.Items.Count;

            Logger.ScrollIntoView(Logger.Items[items - 1]);
        }

        /// <summary>
        /// Clears the logger window in the sidebar when the clearing icon gets clicked
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Clear_Logger(object sender, MouseButtonEventArgs e)
        {
            Logger.Items.Clear();
        }

        /// <summary>
        /// Tries to establish a connection to the pancam camera
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Connect_Pancam(object sender, RoutedEventArgs e)
        {
            LogMessage("Attempt to connect...");
            LogMessage("Connection failed");
        }

        private void ToggleTab(object sender, RoutedEventArgs e)
        {
            int tabToToggleIndex = 0;

            if (sender.GetType() == typeof(MenuItem))
            {
                Int32.TryParse((sender as MenuItem).Tag.ToString(), out tabToToggleIndex);
            }
            else
            {
                Int32.TryParse((sender as Button).Tag.ToString(), out tabToToggleIndex);
            }

            TabItem tabToToggle = ImageTab.Items[tabToToggleIndex] as TabItem;

            if(tabToToggle.Visibility == Visibility.Visible){
                tabToToggle.Visibility = Visibility.Collapsed;
            } 
            else
            {
                tabToToggle.Visibility = Visibility.Visible;
            }
        }

        /// <summary>
        /// Called when the exposure has been changed by one of the buttons (up/down)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ExposureChanged(object sender, RoutedEventArgs e)
        {
            string command = (sender as Button).Tag.ToString();
            string currentExposure = ExposureBox.Text;

            char[] trimmer = { 'm', 's' };

            int miliseconds = 0;
            Int32.TryParse(currentExposure.TrimEnd(trimmer), out miliseconds);

            switch (command)
            {
                case "up":
                    if (++miliseconds > 999)
                    {
                        miliseconds = 999;
                    }
                    break;
                default:
                    if (--miliseconds < 0)
                    {
                        miliseconds = 0;
                    }
                    break;
            }

            ExposureBox.Text = miliseconds + " ms";
        }

        /// <summary>
        /// Called when the exposure text box loses focus
        /// Transforms the user input into a valid format or swaps an error message
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ExposureLostFocus(object sender, RoutedEventArgs e)
        {
            string text = ExposureBox.Text;

            char[] trimmer = { 'm', 's' };
            int miliseconds = 0;
            if (!Int32.TryParse(text.TrimEnd(trimmer), out miliseconds))
            {
                MessageBox.Show("Please enter an integer.");
                ExposureBox.Text = "100 ms";
                return; ;
            }
            ExposureBox.Text = miliseconds + " ms";
        }

        /// <summary>
        /// Button click listener to display (toggle) the pixel overlay
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ShowPixelOverlay(object sender, RoutedEventArgs e)
        {
            Button source = sender as Button;

            if (showPixelOverlay)
            {
                showPixelOverlay = false;
                (source.Content as Image).Source = new BitmapImage(new Uri(@"pack://application:,,,/spacex.util;component/Icons/pixel.png"));
                source.Style = Application.Current.FindResource("IconButton") as Style;
                return;
            }
            showPixelOverlay = true;
            (source.Content as Image).Source = new BitmapImage(new Uri(@"pack://application:,,,/spacex.util;component/Icons/pixelDown.png"));
            source.Style = Application.Current.FindResource("IconButtonToggle") as Style;
        }

        /// <summary>
        /// Adjusts the positon and color of the pixel overlay 
        /// Called when the user moves the mouse over the pancam image
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MouseMoveImage(object sender, MouseEventArgs e)
        {
            Point pixelPosition = new Point();
            Color color = SamplePixelForColor(sender as Image, out pixelPosition);

            RLabel.Content = String.Format("R:{0,3}", color.R);
            GLabel.Content = String.Format("G:{0,3}", color.G);
            BLabel.Content = String.Format("B:{0,3}", color.B);

            PixelColor.Background = new SolidColorBrush(color);

            PixelX.Content = String.Format("X:{0,4}", (int)pixelPosition.X);
            PixelY.Content = String.Format("Y:{0,4}", (int)pixelPosition.Y);

            Point mousePoint = this.PointToScreen(Mouse.GetPosition(this));

            pixelOverlay.Top = mousePoint.Y + 10;
            pixelOverlay.Left = mousePoint.X + 10;
            pixelOverlay.SetPixelColor(color);
        }

        /// <summary>
        /// When the mouse enters the PanCam image the pixel overlay is displayed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MouseEntersPanCamImage(object sender, MouseEventArgs e)
        {
            if (showPixelOverlay)
            {
                pixelOverlay.Show();
            }
        }

        /// <summary>
        /// If the mouse leaves the pancam image, the pixel overlay is hidden
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MouseLeavesPanCamImage(object sender, MouseEventArgs e)
        {
            pixelOverlay.Hide();
        }

        /// <summary>
        /// Toggles the way the pixels are rendered to the screen (Precise or nearest neighbour)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ToggleRender(object sender, RoutedEventArgs e)
        {
            if (scalingAA)
            {
                RenderOptions.SetBitmapScalingMode(PanCam, BitmapScalingMode.NearestNeighbor);
                RenderOptions.SetBitmapScalingMode(PanCamLeft, BitmapScalingMode.NearestNeighbor);
                RenderOptions.SetBitmapScalingMode(PanCamRight, BitmapScalingMode.NearestNeighbor);
                LogMessage("Rendering Nearest Neighbour");
                scalingAA = false;
            }
            else
            {
                RenderOptions.SetBitmapScalingMode(PanCam, BitmapScalingMode.HighQuality);
                RenderOptions.SetBitmapScalingMode(PanCamLeft, BitmapScalingMode.HighQuality);
                RenderOptions.SetBitmapScalingMode(PanCamRight, BitmapScalingMode.HighQuality);
                LogMessage("Rendering Hight Quality");
                scalingAA = true;
            }
        }

        /// <summary>
        /// Toggles a border element withing the sidebar
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ToggleBorder(object sender, RoutedEventArgs e)
        {
            Button trigger = (sender as Button);
            Border toggleBorder = ControlPanel.FindName(trigger.Name + "Border") as Border;

            DoubleAnimation da = new DoubleAnimation();
            da.EasingFunction = new CubicEase(); ;

            if (toggleBorder.ActualHeight < toggleBorder.MaxHeight)
            {
                da.From = toggleBorder.ActualHeight;
                da.To = toggleBorder.MaxHeight;

                (trigger.Content as Image).Source = new BitmapImage(new Uri(@"pack://application:,,,/spacex.util;component/Icons/collapse.png"));
                (trigger.Content as Image).Uid = "collapse";
            }
            else
            {
                da.From = toggleBorder.ActualHeight;
                da.To = 10;

                (trigger.Content as Image).Source = new BitmapImage(new Uri(@"pack://application:,,,/spacex.util;component/Icons/minimize.png"));
                (trigger.Content as Image).Uid = "minimize";
            }

            da.Duration = new Duration(TimeSpan.FromSeconds(0.5));
            toggleBorder.BeginAnimation(Border.HeightProperty, da);
        }

        #region Utility Methods
        /// <summary>
        /// Samples the pixel color from a point
        /// </summary>
        /// <param name="pixelPosition"></param>
        /// <returns></returns>
        private Color SamplePixelForColor(Image targetImage, out Point pixelPosition)
        {
            // Retrieve the coordinate of the mouse position in relation to the supplied image.
            Point point = Mouse.GetPosition(targetImage);

            // Use RenderTargetBitmap to get the visual, in case the image has been transformed.
            var renderTargetBitmap = new RenderTargetBitmap((int)targetImage.ActualWidth, (int)targetImage.ActualHeight, 96, 96, PixelFormats.Default);
            renderTargetBitmap.Render(targetImage);

            pixelPosition = new Point();

            pixelPosition.X = point.X;
            pixelPosition.Y = point.Y;

            // Make sure that the point is within the dimensions of the image.
            if ((point.X <= renderTargetBitmap.PixelWidth) && (point.Y <= renderTargetBitmap.PixelHeight))
            {
                // Create a cropped image at the supplied point coordinates.
                CroppedBitmap croppedBitmap;

                try
                {
                    croppedBitmap = new CroppedBitmap(renderTargetBitmap, new Int32Rect((int)point.X, (int)point.Y, 1, 1));
                }
                catch
                {
                    return Color.FromArgb(255, 0, 0, 0);
                }

                // Copy the sampled pixel to a byte array.
                var pixels = new byte[4];
                croppedBitmap.CopyPixels(pixels, 4, 0);

                // Assign the sampled color to a SolidColorBrush and return as conversion.
                return Color.FromArgb(255, pixels[2], pixels[1], pixels[0]);
            }

            return Color.FromArgb(0, 0, 0, 0);
        }

        /// <summary>
        /// Extract a horizontal image cut
        /// </summary>
        /// <param name="targetImage"></param>
        /// <returns></returns>
        private List<int> ExtractImageCut(Image targetImage, out int pixelRow)
        {
            // Retrieve the coordinate of the mouse position in relation to the supplied image.
            Point point = Mouse.GetPosition(targetImage);

            pixelRow = (int)point.Y;

            //MessageBox.Show("Pint: " + point.ToString());

            List<int> brightnessArray = new List<int>();

            // Use RenderTargetBitmap to get the visual, in case the image has been transformed.
            var renderTargetBitmap = new RenderTargetBitmap((int)targetImage.ActualWidth, (int)targetImage.ActualHeight, 96, 96, PixelFormats.Default);
            renderTargetBitmap.Render(targetImage);

            // Make sure that the point is within the dimensions of the image.
            if ((point.X <= renderTargetBitmap.PixelWidth) && (point.Y <= renderTargetBitmap.PixelHeight))
            {
                point.X = 0;

                // Create a cropped image at the supplied point coordinates.
                BitmapImage source = targetImage.Source as BitmapImage;
                int imageWidth = source.PixelWidth;

                CroppedBitmap croppedBitmap = new CroppedBitmap(renderTargetBitmap, new Int32Rect((int)point.X, (int)point.Y, imageWidth-1, 1));

                // Copy the sampled pixel to a byte array.
                byte[] pixels = new byte[4 * imageWidth];
                croppedBitmap.CopyPixels(pixels, 4 * imageWidth, 0);

                for (int i = 0; i < pixels.Length; i += 4 )
                {
                    brightnessArray.Add(pixels[i] + pixels[i+1] + pixels[i+2]);   
                }

                // Return the brightness array
                return brightnessArray;
            }

            return null;
        }

        #endregion

        /// <summary>
        /// Toggles wether the pixture will be auto or manually exposed by the camera
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ToggleExposure(object sender, RoutedEventArgs e)
        {
            Button source = sender as Button;

            if(autoExposure){
                ExposureBox.IsEnabled = true;
                exUp.IsEnabled = true;
                exDown.IsEnabled = true;

                (source.Content as Image).Source = new BitmapImage(new Uri(@"pack://application:,,,/spacex.util;component/Icons/auto.png"));
                source.Style = Application.Current.FindResource("IconButton") as Style;

                autoExposure = false;
                auto.ToolTip = "Auto Exposure";

                LogMessage("Auto exposure disbaled");

            }
            else{
                ExposureBox.IsEnabled = false;
                exUp.IsEnabled = false;
                exDown.IsEnabled = false;

                (source.Content as Image).Source = new BitmapImage(new Uri(@"pack://application:,,,/spacex.util;component/Icons/autoDown.png"));
                source.Style = Application.Current.FindResource("IconButtonToggle") as Style;
                autoExposure = true;
                auto.ToolTip = "Manual Exposure";

                LogMessage("Auto exposure enabled");
            }
        }

        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            (sender as Button).Background = Brushes.LightGray;
        }

        /// <summary>
        /// Called when the application closes, stores preferences in user settings file
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ApplicationClose(object sender, System.ComponentModel.CancelEventArgs e)
        {
            Settings.Default.WindowSize = new System.Drawing.Point((int)this.Width,(int)this.Height);
            Settings.Default.Save();
        }

        private void SelectStereoImage(object sender, MouseButtonEventArgs e)
        {
            cameraViewModel.inStereo = true;
        }

        /// <summary>
        /// Click listener on the pancam image (main view)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ClickOnPanCam(object sender, MouseButtonEventArgs e)
        {
            int pixelRow = 0;
            cameraViewModel.cutArray = this.ExtractImageCut(PanCam, out pixelRow);
            cameraViewModel.pixelRow = pixelRow;
        }

        private void ClearImageCut(object sender, RoutedEventArgs e)
        {
            cameraViewModel.ClearImageCut();
        }
    }
}
